package captcha

import (
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	"image/jpeg"
	"image/png"
	"math/rand"
	"os"
	"path/filepath"
	"strings"
	"time"

	"go-mgdms/internal/common/models"
	"go-mgdms/internal/common/utils"

	"github.com/golang/freetype"
	"github.com/golang/freetype/truetype"

	"maps"

	"golang.org/x/image/font"
	"gorm.io/gorm"
)

// TextItem 验证点文本项
type TextItem struct {
	Size   int    `json:"size"`
	Icon   bool   `json:"icon"`
	Name   string `json:"name,omitempty"`
	Text   string `json:"text"`
	Width  int    `json:"width"`
	Height int    `json:"height"`
	X      int    `json:"x,omitempty"`
	Y      int    `json:"y,omitempty"`
}

// CaptchaData 验证码数据
type CaptchaData struct {
	Text   []TextItem `json:"text"` // 调整到第一位
	Width  int        `json:"width"`
	Height int        `json:"height"`
}

// ClickCaptcha 点选文字验证码类
type ClickCaptcha struct {
	expire    int               // 验证码过期时间(s)
	bgPaths   []string          // 可以使用的背景图片路径
	fontPaths []string          // 可以使用的字体文件路径
	iconDict  map[string]string // 验证点 Icon 映射表
	config    map[string]interface{}
	db        *gorm.DB
}

// NewClickCaptcha 创建验证码实例
func NewClickCaptcha(db *gorm.DB, config map[string]any) *ClickCaptcha {
	// 默认配置
	defaultConfig := map[string]any{
		"alpha":          36,                       // 透明度
		"length":         4,                        // 验证码长度
		"confuse_length": 2,                        // 混淆字符数量
		"mode":           []string{"text", "icon"}, // 验证模式
	}

	// 合并配置
	maps.Copy(defaultConfig, config)

	// 定义基础资源目录
	resourceDir := "public" // 可以从配置中获取

	cc := &ClickCaptcha{
		expire: 600, // 10分钟过期
		bgPaths: []string{
			filepath.Join(resourceDir, "static", "images", "captcha", "click", "bgs", "1.png"),
			filepath.Join(resourceDir, "static", "images", "captcha", "click", "bgs", "2.png"),
			filepath.Join(resourceDir, "static", "images", "captcha", "click", "bgs", "3.png"),
		},
		fontPaths: []string{
			filepath.Join(resourceDir, "static", "fonts", "zhttfs", "SourceHanSansCN-Normal.ttf"),
		},
		iconDict: map[string]string{
			"aeroplane": "飞机",
			"apple":     "苹果",
			"banana":    "香蕉",
			"bell":      "铃铛",
			"bicycle":   "自行车",
			"bird":      "小鸟",
			"bomb":      "炸弹",
			"butterfly": "蝴蝶",
			"candy":     "糖果",
			"crab":      "螃蟹",
			"cup":       "杯子",
			"dolphin":   "海豚",
			"fire":      "火",
			"guitar":    "吉他",
			"hexagon":   "六角形",
			"pear":      "梨",
			"rocket":    "火箭",
			"sailboat":  "帆船",
			"snowflake": "雪花",
			"wolf head": "狼头",
		},
		config: defaultConfig,
		db:     db,
	}

	// 清理过期的验证码
	go cc.cleanExpiredCaptchas()

	return cc
}

// 清理过期验证码
func (cc *ClickCaptcha) cleanExpiredCaptchas() {
	cc.db.Where("expire_time < ?", time.Now()).Delete(&models.CaptchaModel{})
}

// Create 创建图形验证码
func (cc *ClickCaptcha) Create(id string) (map[string]interface{}, error) {
	// 随机选择背景图片和字体
	rand.Seed(time.Now().UnixNano())
	imagePath := cc.bgPaths[rand.Intn(len(cc.bgPaths))]
	fontPath := cc.fontPaths[rand.Intn(len(cc.fontPaths))]

	// 计算验证码长度和混淆字符长度
	length := cc.config["length"].(int)
	confuseLength := cc.config["confuse_length"].(int)
	totalLength := length + confuseLength

	// 随机生成验证点
	randPoints := cc.randPoints(totalLength)

	// 加载背景图片
	bgImg, imgType, err := cc.loadImage(imagePath)
	if err != nil {
		return nil, err
	}

	// 获取图片尺寸
	bounds := bgImg.Bounds()
	width := bounds.Max.X
	height := bounds.Max.Y

	// 准备文字数据
	var textArr CaptchaData
	textArr.Width = width
	textArr.Height = height
	textArr.Text = make([]TextItem, 0, totalLength)

	// 加载字体
	ft, err := cc.loadFont(fontPath)
	if err != nil {
		return nil, err
	}

	// 创建文本数据
	var textList []string
	for _, v := range randPoints {
		tmp := TextItem{
			Size: rand.Intn(16) + 15, // 15-30
		}

		if iconName, ok := v.(string); ok && cc.iconExists(iconName) {
			// 图标
			tmp.Icon = true
			tmp.Name = iconName

			// 获取图标中文名
			iconText := iconName
			if chName, exists := cc.iconDict[iconName]; exists {
				iconText = chName
			}

			tmp.Text = "<" + iconText + ">"

			// 获取图标尺寸
			iconWidth, iconHeight, err := cc.getIconSize(iconName)
			if err != nil {
				return nil, err
			}
			tmp.Width = iconWidth
			tmp.Height = iconHeight
		} else {
			// 字符
			tmp.Icon = false
			tmp.Text = v.(string)

			// 计算文字尺寸
			textWidth, textHeight, err := cc.calculateTextSize(ft, tmp.Size, tmp.Text)
			if err != nil {
				return nil, err
			}
			tmp.Width = textWidth
			tmp.Height = textHeight
		}

		textArr.Text = append(textArr.Text, tmp)
		textList = append(textList, tmp.Text)
	}

	// 随机生成验证点位置
	for i := range textArr.Text {
		x, y := cc.randPosition(textArr.Text, textArr.Width, textArr.Height, textArr.Text[i].Width, textArr.Text[i].Height, textArr.Text[i].Icon)
		textArr.Text[i].X = x
		textArr.Text[i].Y = y
	}

	// 创建绘图画布
	rgba := image.NewRGBA(bounds)
	draw.Draw(rgba, bounds, bgImg, image.Point{}, draw.Src)

	// 绘制文字和图标
	for _, item := range textArr.Text {
		if item.Icon {
			cc.drawIcon(rgba, item)
		} else {
			cc.drawText(rgba, item, ft)
		}
	}

	// 取前几个作为有效验证点
	validItems := textArr.Text[:length]
	validTexts := textList[:length]

	// 更新验证码数据只保留有效验证点
	textArr.Text = validItems

	// 转换为JSON
	captchaJSON, err := json.Marshal(textArr)
	if err != nil {
		return nil, err
	}

	// 生成验证码并保存到数据库
	nowTime := time.Now()
	expireTime := nowTime.Add(time.Duration(cc.expire) * time.Second)

	// 计算验证码哈希
	codeHash := fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(validTexts, ","))))

	// 保存到数据库
	captcha := &models.CaptchaModel{
		Key:        fmt.Sprintf("%x", md5.Sum([]byte(id))),
		Code:       codeHash,
		Captcha:    string(captchaJSON),
		CreateTime: uint64(nowTime.Unix()),    // 转换为时间戳
		ExpireTime: uint64(expireTime.Unix()), // 转换为时间戳
	}

	// 使用正确的SQL语法 - 为关键字添加反引号
	var existingCaptcha models.CaptchaModel
	result := cc.db.Where("`key` = ?", captcha.Key).First(&existingCaptcha)

	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			// 记录不存在，创建新记录
			if err := cc.db.Create(captcha).Error; err != nil {
				return nil, fmt.Errorf("创建验证码记录失败: %w", err)
			}
		} else {
			// 其他数据库错误
			return nil, fmt.Errorf("查询验证码失败: %w", result.Error)
		}
	} else {
		// 记录存在，更新
		if err := cc.db.Model(&existingCaptcha).Updates(captcha).Error; err != nil {
			return nil, fmt.Errorf("更新验证码失败: %w", err)
		}
	}

	// 转换图片为base64
	base64Img, err := cc.imageToBase64(rgba, imgType)
	if err != nil {
		return nil, err
	}

	// 返回结果
	return map[string]interface{}{
		"id":     id,
		"text":   validTexts,
		"base64": base64Img,
		"width":  textArr.Width,
		"height": textArr.Height,
	}, nil
}

// Check 检查验证码
func (cc *ClickCaptcha) Check(id string, info string, unset bool) bool {
	key := fmt.Sprintf("%x", md5.Sum([]byte(id)))

	// 添加调试信息
	fmt.Printf("验证ID: %s, 信息: %s\n", id, info)

	// 从数据库获取验证码
	var captcha models.CaptchaModel
	if err := cc.db.Where("`key` = ?", key).First(&captcha).Error; err != nil {
		fmt.Printf("验证码不存在: %v\n", err)
		return false
	}

	// 验证码过期
	if time.Now().Unix() > int64(captcha.ExpireTime) {
		fmt.Println("验证码已过期")
		cc.db.Delete(&captcha)
		return false
	}

	// 解析验证码数据
	var textArr CaptchaData
	if err := json.Unmarshal([]byte(captcha.Captcha), &textArr); err != nil {
		fmt.Printf("JSON解析失败: %v\n", err)
		return false
	}

	// 解析用户点击信息
	parts := strings.Split(info, ";")
	if len(parts) != 3 {
		fmt.Printf("格式错误: 分段数量=%d, 期望=3\n", len(parts))
		return false
	}

	xy := parts[0]
	w := utils.ParseInt(parts[1])
	h := utils.ParseInt(parts[2])

	fmt.Printf("点击信息: xy=%s, w=%d, h=%d\n", xy, w, h)
	fmt.Printf("验证码数据: 宽=%d, 高=%d\n", textArr.Width, textArr.Height)

	// 计算比例
	xPro := float64(w) / float64(textArr.Width)
	yPro := float64(h) / float64(textArr.Height)
	fmt.Printf("缩放比例: xPro=%.4f, yPro=%.4f\n", xPro, yPro)

	// 检查每个点击位置
	xyArr := strings.Split(xy, "-")
	if len(xyArr) != len(textArr.Text) {
		fmt.Printf("点击数量不匹配: 点击=%d, 验证点=%d\n", len(xyArr), len(textArr.Text))
		return false
	}

	for k, v := range xyArr {
		xy := strings.Split(v, ",")
		if len(xy) != 2 {
			fmt.Printf("点击坐标格式错误: %s\n", v)
			return false
		}

		x := utils.ParseFloat(xy[0])
		y := utils.ParseFloat(xy[1])

		// 转换为实际坐标
		actualX := x / xPro
		actualY := y / yPro

		// 调试输出
		fmt.Printf("点击%d: 前端坐标=(%.2f,%.2f), 实际坐标=(%.2f,%.2f)\n", k, x, y, actualX, actualY)

		// 元素信息
		item := textArr.Text[k]
		fmt.Printf("验证点%d: 类型=%s, 位置=(%d,%d), 尺寸=%dx%d\n",
			k,
			map[bool]string{true: "图标", false: "文字"}[item.Icon],
			item.X, item.Y, item.Width, item.Height)

		// 检查X坐标
		if actualX < float64(item.X) || actualX > float64(item.X+item.Width) {
			return false
		}

		// 检查Y坐标
		var phStart, phEnd float64
		if item.Icon {
			phStart = float64(item.Y)
			phEnd = float64(item.Y + item.Height)
		} else {
			phStart = float64(item.Y - item.Height)
			phEnd = float64(item.Y)
		}

		if actualY < phStart || actualY > phEnd {
			return false
		}
	}

	// 验证成功后删除验证码
	if unset {
		cc.db.Delete(&captcha)
	}

	return true
}

// 加载图片
func (cc *ClickCaptcha) loadImage(path string) (image.Image, string, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, "", err
	}
	defer file.Close()

	img, format, err := image.Decode(file)
	return img, format, err
}

// 加载字体
func (cc *ClickCaptcha) loadFont(path string) (*truetype.Font, error) {
	fontData, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	font, err := freetype.ParseFont(fontData)
	if err != nil {
		return nil, err
	}

	return font, nil
}

// 计算文字尺寸
func (cc *ClickCaptcha) calculateTextSize(ft *truetype.Font, fontSize int, text string) (int, int, error) {
	opts := truetype.Options{
		Size: float64(fontSize),
	}
	face := truetype.NewFace(ft, &opts)

	width := font.MeasureString(face, text).Ceil()
	height := face.Metrics().Height.Ceil()

	return width, height, nil
}

// 图标是否存在
func (cc *ClickCaptcha) iconExists(name string) bool {
	_, exists := cc.iconDict[name]
	return exists
}

// 获取图标尺寸
func (cc *ClickCaptcha) getIconSize(name string) (int, int, error) {
	path := filepath.Join("public", "static", "images", "captcha", "click", "icons", name+".png")
	file, err := os.Open(path)
	if err != nil {
		return 0, 0, err
	}
	defer file.Close()

	config, _, err := image.DecodeConfig(file)
	if err != nil {
		return 0, 0, err
	}

	return config.Width, config.Height, nil
}

// 绘制图标
func (cc *ClickCaptcha) drawIcon(dst *image.RGBA, iconData TextItem) error {
	iconPath := filepath.Join("public", "static", "images", "captcha", "click", "icons", iconData.Name+".png")
	iconFile, err := os.Open(iconPath)
	if err != nil {
		return err
	}
	defer iconFile.Close()

	iconImg, _, err := image.Decode(iconFile)
	if err != nil {
		return err
	}

	// 计算图标位置
	iconRect := image.Rect(iconData.X, iconData.Y, iconData.X+iconData.Width, iconData.Y+iconData.Height)

	// 绘制图标，应用透明度
	alpha := cc.config["alpha"].(int)
	cc.drawImageWithAlpha(dst, iconImg, iconRect, alpha)

	return nil
}

// 方法1：使用draw.DrawMask和uniform alpha
func (cc *ClickCaptcha) drawImageWithAlpha(dst *image.RGBA, src image.Image, rect image.Rectangle, alphaPercent int) {
	// 将百分比转换为0-255范围的alpha值
	alphaValue := uint8(alphaPercent * 255 / 100)

	// 创建统一alpha遮罩
	mask := image.NewUniform(color.Alpha{A: alphaValue})

	// 使用遮罩绘制图像
	draw.DrawMask(dst, rect, src, src.Bounds().Min, mask, image.Point{}, draw.Over)
}

// 绘制文字
func (cc *ClickCaptcha) drawText(dst *image.RGBA, textData TextItem, ft *truetype.Font) error {
	// 设置字体参数
	opts := &truetype.Options{
		Size: float64(textData.Size),
	}
	face := truetype.NewFace(ft, opts)

	// 设置文本绘制参数
	d := &font.Drawer{
		Dst:  dst,
		Src:  image.NewUniform(image.White),
		Face: face,
		Dot:  freetype.Pt(textData.X, textData.Y),
	}

	// 绘制文字
	d.DrawString(textData.Text)

	return nil
}

// 随机生成验证点元素（避免重复）
func (cc *ClickCaptcha) randPoints(length int) []any {
	var arr []interface{}
	modes := cc.config["mode"].([]string)

	// 用于检测重复项的映射
	used := make(map[string]bool)

	// 生成足够数量的点
	for len(arr) < length {
		// 随机选择模式
		mode := modes[rand.Intn(len(modes))]

		if mode == "text" {
			// 添加随机中文字符
			zhSet := "们以我到他会作时要动国产的是工就年阶义发成部民可出能方进在和有大这主中"

			// 尝试最多10次找到不重复的字符
			foundUnique := false
			for attempt := 0; attempt < 10 && !foundUnique; attempt++ {
				idx := rand.Intn(len([]rune(zhSet)))
				char := string([]rune(zhSet)[idx : idx+1])

				// 检查是否已使用
				if !used[char] {
					arr = append(arr, char)
					used[char] = true
					foundUnique = true
				}
			}

			// 如果10次尝试都找不到不重复的字符，则扩展字符集
			if !foundUnique {
				// 生成一个随机数字作为备用字符
				backupChar := fmt.Sprintf("%d", rand.Intn(10))
				if !used[backupChar] {
					arr = append(arr, backupChar)
					used[backupChar] = true
				}
			}
		} else if mode == "icon" {
			// 收集未使用的图标
			unusedIcons := make([]string, 0)
			for k := range cc.iconDict {
				// 使用"icon:名称"作为键，避免与文字混淆
				iconKey := "icon:" + k
				if !used[iconKey] {
					unusedIcons = append(unusedIcons, k)
				}
			}

			// 如果所有图标都已使用，则切换到文字模式
			if len(unusedIcons) == 0 {
				// 这里回退到文本模式
				continue
			}

			// 选择一个未使用的图标
			icon := unusedIcons[rand.Intn(len(unusedIcons))]
			arr = append(arr, icon)
			used["icon:"+icon] = true
		}
	}

	// 打乱顺序
	rand.Shuffle(len(arr), func(i, j int) {
		arr[i], arr[j] = arr[j], arr[i]
	})

	return arr[:length]
}

// 随机生成位置布局
func (cc *ClickCaptcha) randPosition(textArr []TextItem, imgW, imgH, fontW, fontH int, isIcon bool) (int, int) {
	// 在图片范围内随机生成位置
	x := rand.Intn(imgW - fontW)
	y := rand.Intn(imgH - fontH)
	if !isIcon {
		y += fontH // 文字的y坐标需要调整
	}

	// 碰撞检测
	if !cc.checkPosition(textArr, x, y, fontW, fontH, isIcon) {
		return cc.randPosition(textArr, imgW, imgH, fontW, fontH, isIcon)
	}

	return x, y
}

// 碰撞验证
func (cc *ClickCaptcha) checkPosition(textArr []TextItem, x, y, w, h int, isIcon bool) bool {
	for _, v := range textArr {
		if v.X == 0 && v.Y == 0 {
			continue // 跳过未设置位置的元素
		}

		// 检查X轴是否有重叠
		flagX := (x+w < v.X) || (x > v.X+v.Width)

		// 计算当前元素和历史元素的显示范围
		var currentPhStart, currentPhEnd int
		if isIcon {
			currentPhStart = y
			currentPhEnd = y + h
		} else {
			currentPhStart = y - h
			currentPhEnd = y
		}

		var historyPhStart, historyPhEnd int
		if v.Icon {
			historyPhStart = v.Y
			historyPhEnd = v.Y + v.Height
		} else {
			historyPhStart = v.Y - v.Height
			historyPhEnd = v.Y
		}

		// 检查Y轴是否有重叠
		flagY := (currentPhEnd < historyPhStart) || (currentPhStart > historyPhEnd)

		// 如果X和Y都有重叠，则位置不可用
		if !flagX && !flagY {
			return false
		}
	}

	return true
}

// 将图像转为base64编码
func (cc *ClickCaptcha) imageToBase64(img *image.RGBA, format string) (string, error) {
	var buf strings.Builder

	// 写入数据URL前缀
	buf.WriteString("data:image/")

	// 根据格式选择编码方式
	var err error
	switch format {
	case "png":
		buf.WriteString("png;base64,")
		err = encodePNGToBase64(&buf, img)
	case "jpeg", "jpg":
		buf.WriteString("jpeg;base64,")
		err = encodeJPEGToBase64(&buf, img, &jpeg.Options{Quality: 90})
	case "gif":
		buf.WriteString("gif;base64,")
		err = encodeGIFToBase64(&buf, img)
	default:
		return "", errors.New("不支持的图像格式")
	}

	if err != nil {
		return "", err
	}

	return buf.String(), nil
}

// PNG编码为base64
func encodePNGToBase64(buf *strings.Builder, img *image.RGBA) error {
	b64Encoder := base64.NewEncoder(base64.StdEncoding, buf)
	err := png.Encode(b64Encoder, img)
	b64Encoder.Close()
	return err
}

// JPEG编码为base64
func encodeJPEGToBase64(buf *strings.Builder, img *image.RGBA, opts *jpeg.Options) error {
	b64Encoder := base64.NewEncoder(base64.StdEncoding, buf)
	err := jpeg.Encode(b64Encoder, img, opts)
	b64Encoder.Close()
	return err
}

// GIF编码为base64
func encodeGIFToBase64(buf *strings.Builder, img *image.RGBA) error {
	b64Encoder := base64.NewEncoder(base64.StdEncoding, buf)
	err := gif.Encode(b64Encoder, img, nil)
	b64Encoder.Close()
	return err
}
